<template>
  <div class="chat">
    <Header />
    <div class="container">
      <div class="l">
        <div class="content" ref="msgContentRef">
          <div
            class="item"
            :class="[item.type == 0 ? 'item_r' : '']"
            v-for="(item, index) in msgList"
            :key="index"
          >
            <div class="avatar">
              <img
                v-if="item.type == 0"
                :src="QNURL + userStore.getUserInfo?.avatar"
                alt=""
              />
              <span v-else>AI</span>
            </div>
            <div class="value" v-html="item.value" />
          </div>
        </div>
        <div class="op">
          <el-input
            v-model="msg"
            placeholder="请输入内容"
            class="input"
            style="margin-left: 10px"
          />
          <el-button style="margin: 0 10px" type="primary" @click="send">{{
            isSend ? "交流中" : "发送"
          }}</el-button>
        </div>
      </div>
      <div class="r">
        <audio id="audio" />
        <video
          ref="video"
          class="video"
          loop
          muted
          src="@/assets/video/1.webm"
        />
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import axios from "axios";
import { ElMessage } from "element-plus";
import { ref, onMounted, onBeforeUnmount, nextTick } from "vue";
import Header from "@/components/header/Header.vue";
import { useUserStoreHook } from "@/store/modules/user";
import { QNURL } from "@/config/urlConfig";

const userStore = useUserStoreHook();

const baseUrl = "http://120.199.181.175:8003/v1/chat/completions";

let msg = ref("");

let msgList = ref<any>([]);

const msgContentRef = ref();

const video = ref();

let isSend = ref(false);

function send() {
  if (isSend.value) return;

  isSend.value = true;

  msgList.value.push({
    value: msg.value,
    type: 0
  });
  scrollBottom();

  const reqData = {
    model: "chatglm3-6b",
    messages: [
      {
        role: "system",
        content: "你是一名老师"
      },
      {
        role: "user",
        content: msg.value
      }
    ],
    stream: false
  };

  msg.value = "";

  msgList.value.push({ type: 1, value: "请求中，请稍后...." });

  axios
    .post(baseUrl, reqData)
    .then(res => {
      // console.log(res);
      const msgRes = res.data?.choices[0]?.message?.content;
      // const len = msgList.value.length;
      // msgList.value[len - 1] = {
      //   value: msgRes,
      //   type: 1
      // };
      printText(msgRes);

      startSpeak(msgRes);

      scrollBottom();
    })
    .catch(_err => {
      // console.log(err);
      ElMessage.error("网络错误，请稍后重试！");
    });
}

function printText(content: string, speed = 150) {
  let index = 0;
  const len = content.length;
  const msgLen = msgList.value.length;
  let res = "";

  let printInterval = setInterval(() => {
    res += content[index];

    msgList.value[msgLen - 1] = {
      value: res,
      type: 1
    };
    scrollBottom();

    index++;

    if (index >= len) {
      index = 0;
      isSend.value = false;
      clearInterval(printInterval);
    }
  }, speed);
}

function scrollBottom() {
  nextTick(() => {
    if (msgContentRef.value.scrollTop != msgContentRef.value.scrollHeight) {
      msgContentRef.value.scrollTop = msgContentRef.value.scrollHeight;
    }
  });
}

// -------------------------------------------------------------------

var voiceList = [
  { ShortName: "zh-CN-XiaoxiaoNeural", label: "Xiaoxiao" },
  { ShortName: "zh-CN-XiaoyiNeural", label: "Xiaoyi" },
  { ShortName: "zh-CN-YunjianNeural", label: "Yunjian" },
  { ShortName: "zh-CN-YunxiNeural", label: "Yunxi" },
  { ShortName: "zh-CN-YunxiaNeural", label: "Yunxia" },
  { ShortName: "zh-CN-YunyangNeural", label: "Yunyang" }
];
let ws: any = null;
let blobs: any = [];
let audioElement: any;

function sendReq(ssml: any, format: any, connectionId: any) {
  let configData = {
    context: {
      synthesis: {
        audio: {
          metadataoptions: {
            sentenceBoundaryEnabled: "false",
            wordBoundaryEnabled: "false"
          },
          outputFormat: format
        }
      }
    }
  };
  let configMessage =
    `X-Timestamp:${Date()}\r\n` +
    "Content-Type:application/json; charset=utf-8\r\n" +
    "Path:speech.config\r\n\r\n" +
    JSON.stringify(configData);
  // console.log(`配置请求发送：${configMessage}\n`);
  let ssmlMessage =
    `X-Timestamp:${Date()}\r\n` +
    `X-RequestId:${connectionId}\r\n` +
    `Content-Type:application/ssml+xml\r\n` +
    `Path:ssml\r\n\r\n` +
    ssml;
  // console.log(`SSML消息发送：${ssmlMessage}\n`);
  ws.send(configMessage, (configError: any) => {
    if (configError) {
      // console.log(`配置请求发送失败：${connectionId}\n`);
    }
  });
  ws.send(ssmlMessage, (ssmlError: any) => {
    if (ssmlError) {
      // console.log(`SSML消息发送失败：${connectionId}\n`);
    }
  });
}

function generateRandomHex() {
  // 创建一个长度为 16 字节的 Uint8Array
  const randomBytes = new Uint8Array(16);
  // 填充数组的每个元素为一个随机的 0-255 之间的整数
  window.crypto.getRandomValues(randomBytes);
  // 将字节数组转换为十六进制字符串，并将字母转换为小写
  const hexString = Array.from(randomBytes)
    .map(byte => byte.toString(16).padStart(2, "0"))
    .join("")
    .toLowerCase();
  return hexString;
}

async function connect(ssml: string, format: string, autpPlay: boolean) {
  return new Promise((resolve, _reject) => {
    // console.log(reject);
    const connectionId = generateRandomHex();
    let url = `wss://speech.platform.bing.com/consumer/speech/synthesize/readaloud/edge/v1?TrustedClientToken=6A5AA1D4EAFF4E9FB37E23D68491D6F4&ConnectionId=${connectionId}`;
    ws = new window.WebSocket(url);
    ws.onopen = () => {
      // console.log("wsOpen");
      sendReq(ssml, format, connectionId);
    };
    ws.onclose = () => {
      // 服务器会自动断开空闲超过30秒的连接
      ws = null;
      blobs = [];
      // console.log(`连接已关闭： ${reason} ${code}`);
    };
    ws.onmessage = (message: any) => {
      if (!(message.data instanceof Blob)) {
        let data = message.data.toString();
        if (data.includes("Path:turn.start")) {
          // console.log("开始传输");
          // 开始传输
        } else if (data.includes("Path:turn.end")) {
          // console.log("+结束传输++++++++++++++==");
          // 结束传输
          for (let i = 0; i < blobs.length; i++) {
            let contentIndex = 130;
            if (i == blobs.length - 1) {
              contentIndex = 105;
            }
            blobs[i] = blobs[i].slice(contentIndex);
          }
          let result = new Blob(blobs);
          let url = URL.createObjectURL(result);
          if (autpPlay) {
            // console.log(audioElement);
            audioElement.pause();
            audioElement.src = url;
            audioElement.play();
            video.value.play();

            const audioEnd = () => {
              video.value.pause();
              audioElement.removeEventListener("ended", audioEnd);
            };

            audioElement.addEventListener("ended", audioEnd);
          }
          blobs = [];
          ws.close();
          // console.log(`传输完成：${url}`);
          resolve(url);
        }
      } else if (message.data instanceof Blob) {
        // console.log("收到信号了b......", message.data);
        blobs.push(message.data);
      }
    };
    ws.onerror = (error: any) => {
      console.log(`连接失败： ${error}`);
    };
  });
}

async function start(
  text: string,
  voice = 0,
  rate = 0,
  pitch = 0,
  autpPlay = true
) {
  if (text) {
    let SSML = "";
    // console.log("text", text);
    // console.log("voice", voiceList[voice].ShortName);
    // console.log("rate", rate);
    // console.log("pitch", pitch);
    SSML = `
    <speak xmlns="http://www.w3.org/2001/10/synthesis" xmlns:mstts="http://www.w3.org/2001/mstts" xmlns:emo="http://www.w3.org/2009/10/emotionml" version="1.0" xml:lang="en-US">
        <voice name="${voiceList[voice].ShortName}">
            <prosody rate="${rate}%" pitch="${pitch}%">
            ${text}
            </prosody>
        </voice>
    </speak>`;
    // console.log(SSML);
    let format = "audio-24khz-48kbitrate-mono-mp3";
    let result = await connect(SSML, format, autpPlay).then(result => {
      // console.log("Received result:", result);
      return result;
    });
    return result;
  }
}

const startSpeak = async (text: string) => {
  await start(text, 0, 0, 0);
  // console.log(url);
};

// -------------------------------------------------------------------
onMounted(() => {
  audioElement = document.getElementById("audio");
  if (localStorage.getItem("msgList")) {
    msgList.value = JSON.parse(localStorage.getItem("msgList") as string);
    scrollBottom();
  }
});

onBeforeUnmount(() => {
  localStorage.setItem("msgList", JSON.stringify(msgList.value));
});
</script>

<style lang="scss" scoped>
.chat {
  width: 100%;
  height: 100vh;

  .container {
    width: 100%;
    height: calc(100% - 80px);
    display: flex;
    min-width: 1200px;
    background-color: var(--bg-color);

    .l,
    .r {
      height: 100%;
    }

    .l {
      flex: 1;
      order: 2;
      .op {
        width: 100%;
        height: 50px;
        background-color: #fff;
        display: flex;
        align-items: center;
        // padding: 5px 0;
      }

      .content {
        width: 100%;
        height: calc(100% - 50px);
        padding: 20px;
        overflow-y: scroll;
        transition: all 300ms;

        &::-webkit-scrollbar {
          width: 0;
        }

        .item {
          width: 100%;
          display: flex;
          margin-bottom: 10px;
          padding-right: 80px;

          .avatar {
            width: 50px;
            height: 50px;
            border-radius: 50%;
            overflow: hidden;
            margin-right: 10px;
            display: flex;
            align-items: center;
            justify-content: center;
            border: 2px solid #fff;

            img {
              width: 100%;
              height: 100%;
            }

            span {
              font-size: 20px;
              font-weight: bold;
              color: var(--orange-color);
            }
          }

          .value {
            min-width: 20px;
            max-width: calc(100% - 120px);
            padding-top: 10px;
            line-height: 25px;
            background-color: #fff;
            padding: 10px;
            border-radius: 10px;
          }
        }
        .item_r {
          @extend .item;
          justify-content: flex-end;
          padding-right: 0;
          padding-left: 80px;

          .avatar {
            order: 2;
            margin-right: 0;
            margin-left: 10px;
          }

          .value {
            text-align: right;
          }
        }
      }
    }

    .r {
      border-right: 5px solid #ccc;
      width: 40%;
      display: flex;
      align-items: center;
      justify-content: center;
      .video {
        width: 100%;

        &::-webkit-media-controls-enclosure {
          display: none !important;
        }
      }
    }
  }
}
</style>
